package com.acme.hashmap;

import com.acme.hash.HashConvolution;
import com.acme.hash.SmearIntHashFunctionImpl;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

/**
 * @author Gregory Sobko
 */
public class ByteBufferLinkedHashMapIntToInt implements MapIntToInt {

    public final int NULL_POINTER = -1;

    private final int hashCapacity;

    private final int linkedStorageCapacity ;

    private final int hashOffset;

    private int linkedStoragePointer = 0;

    private int size = 0;

    private int FIRST_REMOVED_INDEX = NULL_POINTER;

    private HashConvolution hashFunction;

    private IntBuffer storage;

    public ByteBufferLinkedHashMapIntToInt(ByteBuffer storage){
        this.storage = storage.asIntBuffer();
        int totalCapacity = this.storage.capacity();
        int capacity = (totalCapacity - (totalCapacity % 4)) / 4;
        hashOffset = 3 * capacity;
        linkedStorageCapacity = hashCapacity = capacity;
        hashFunction = new HashConvolution(new SmearIntHashFunctionImpl());
        hashFunction.setDimension(hashCapacity);
        initHashTable();
        initLinkedStorage();
    }

    @Override
    public Integer put(int key, int value){
        int hash = this.hashFunction.hash(key);
        int firstIndex = getAddressForStoredHash(hash);
        int previous;
        if (firstIndex == NULL_POINTER) {
            incrementSize();
            setAddressForStoredHash(hash, putElement(linkedStoragePointer, key, value));
        } else {
            int currentIndex = firstIndex;
            int nextIndex = currentIndex;
            do{
                if(key == getElementKey(nextIndex)){
                    previous = getElementValue(nextIndex);
                    setElementValue(nextIndex, value);
                    return previous;
                }
                currentIndex = nextIndex;
            } while((nextIndex = getElementNext(nextIndex)) != NULL_POINTER);
            incrementSize();
            setElementNext(currentIndex, putElement(linkedStoragePointer, key, value));
        }
        return null;
    }

    @Override
    public Integer get(int key) {
        int hash = this.hashFunction.hash(key);
        return getValueByKey(getAddressForStoredHash(hash), key);
    }

    @Override
    public boolean contains(int key) {
        return get(key) != null ;
    }

    @Override
    public Integer remove(int key) {
        int hash = this.hashFunction.hash(key);
        int firstIndex = getAddressForStoredHash(hash);
        int previous;
        if (firstIndex == NULL_POINTER) {
            return null;
        } else {

        }
        return null;
    }

    @Override
    public void clear() {
        initHashTable();
        initLinkedStorage();
        size = 0;
        linkedStoragePointer = 0;
    }

    @Override
    public int size() {
        return size;
    }

    private Integer getValueByKey(int index, int key){
        if(index == NULL_POINTER) {
            return null;
        } else if(getElementKey(index) == key) {
            return getElementValue(index) ;
        } else {
            return getValueByKey(getElementNext(index), key);
        }
    }

    private void incrementSize(){
        size++;
        linkedStoragePointer = nextLinkedStoragePointer();
    }

    private int nextLinkedStoragePointer() {
        linkedStoragePointer++;
        if(linkedStoragePointer==linkedStorageCapacity){

        }

    }




    private int getElementKey(int storageIndex){
        return storage.get(3 * storageIndex);
    }

    private int getElementValue(int storageIndex){
        return storage.get(3 * storageIndex + 1);
    }

    private int getElementNext(int storageIndex){
        return storage.get(3 * storageIndex + 2);
    }

    private void setElementKey(int storageIndex, int key){
        storage.put(3 * storageIndex, key);
    }

    private void setElementValue(int storageIndex, int value){
        storage.put(3 * storageIndex + 1, value);
    }

    private void setElementNext(int storageIndex, int next){
        storage.put(3 * storageIndex + 2, next);
    }
    
    private void setAddressForStoredHash(int index, int address){
        storage.put(hashOffset + index, address);
        
    }
    
    private int getAddressForStoredHash(int index){
        return storage.get(hashOffset + index);
    }
    
    private int putElement(int storageIndex, int key, int value){
        setElementKey(storageIndex, key);
        setElementValue(storageIndex, value);
        setElementNext(storageIndex, NULL_POINTER);
        return storageIndex;
    }

    private void initHashTable(){
        for (int i = 0; i < hashCapacity; i++) {
            setAddressForStoredHash(i, NULL_POINTER);
        }
    }

    private void initLinkedStorage(){
        clearLinkedStorageLinks();
    }

    private void clearLinkedStorageLinks(){
        for (int i = 0; i < linkedStorageCapacity; i++) {
            setElementNext(i, NULL_POINTER);
        }
    }

}
